
var base = require('./base');
var BaseFormatter = base.BaseFormatter;

var HtmlFormatter = function HtmlFormatter() {
};

HtmlFormatter.prototype = new BaseFormatter();

HtmlFormatter.prototype.typeFormattterErrorFormatter = function(context, err) {
    context.out('<pre class="jsondiffpatch-error">' + err + '</pre>');
};

HtmlFormatter.prototype.formatValue = function(context, value) {
    context.out('<pre>' + JSON.stringify(value, null, 2) + '</pre>');
};

HtmlFormatter.prototype.formatTextDiffString = function(context, value) {
    var lines = this.parseTextDiff(value);
    context.out('<ul class="jsondiffpatch-textdiff">');
    for (var i = 0, l = lines.length; i < l; i++) {
        var line = lines[i];
        context.out('<li>' +
        '<div class="jsondiffpatch-textdiff-location">' +
        '<span class="jsondiffpatch-textdiff-line-number">' +
        line.location.line +
        '</span>' +
        '<span class="jsondiffpatch-textdiff-char">' +
        line.location.chr +
        '</span>' +
        '</div>' +
        '<div class="jsondiffpatch-textdiff-line">');
        var pieces = line.pieces;
        for (var pieceIndex = 0, piecesLength = pieces.length; pieceIndex < piecesLength; pieceIndex++) {
            var piece = pieces[pieceIndex];
            context.out('<span class="jsondiffpatch-textdiff-' + piece.type + '">' +
                piece.text + '</span>');
        }
        context.out('</div></li>');
    }
    context.out('</ul>');
};

var adjustArrows = function jsondiffpatchHtmlFormatterAdjustArrows(node) {
    node = node || document;
    var getElementText = function(el) {
        return el.textContent || el.innerText;
    };
    var eachByQuery = function(el, query, fn) {
        var elems = el.querySelectorAll(query);
        for (var i = 0, l = elems.length; i < l; i++) {
            fn(elems[i]);
        }
    };
    var eachChildren = function(el, fn) {
        for (var i = 0, l = el.children.length; i < l; i++) {
            fn(el.children[i], i);
        }
    };
    eachByQuery(node, '.jsondiffpatch-arrow', function(arrow) {
        var arrowParent = arrow.parentNode;
        var svg = arrow.children[0], path = svg.children[1];
        svg.style.display = 'none';
        var destination = getElementText(arrowParent.querySelector('.jsondiffpatch-moved-destination'));
        var container = arrowParent.parentNode;
        var destinationElem;
        eachChildren(container, function(child) {
            if (child.getAttribute('data-key') === destination) {
                destinationElem = child;
            }
        });
        if (!destinationElem) { return; }
        try {
            var distance = destinationElem.offsetTop - arrowParent.offsetTop;
            svg.setAttribute('height', Math.abs(distance) + 6);
            arrow.style.top = (- 8 + (distance > 0 ? 0 : distance)) + 'px';
            var curve = distance > 0 ?
                'M30,0 Q-10,' + Math.round(distance / 2) + ' 26,' + (distance - 4) :
                'M30,' + (-distance) + ' Q-10,' + Math.round(-distance / 2) + ' 26,4';
            path.setAttribute('d', curve);
            svg.style.display = '';
        } catch(err) {
            return;
        }
    });
};

HtmlFormatter.prototype.rootBegin = function(context, type, nodeType) {
    var nodeClass = 'jsondiffpatch-' + type +
        (nodeType ? ' jsondiffpatch-child-node-type-' + nodeType : '');
    context.out('<div class="jsondiffpatch-delta ' + nodeClass + '">');
};

HtmlFormatter.prototype.rootEnd = function(context) {
    context.out('</div>' + (context.hasArrows ?
        ('<script type="text/javascript">setTimeout(' +
            adjustArrows.toString() +
            ',10);</script>') : ''));
};

HtmlFormatter.prototype.nodeBegin = function(context, key, leftKey, type, nodeType) {
    var nodeClass = 'jsondiffpatch-' + type +
        (nodeType ? ' jsondiffpatch-child-node-type-' + nodeType : '');
    context.out('<li class="' + nodeClass + '" data-key="' + leftKey + '">' +
        '<div class="jsondiffpatch-property-name">' + leftKey + '</div>');
};


HtmlFormatter.prototype.nodeEnd = function(context) {
    context.out('</li>');
};

/* jshint camelcase: false */

HtmlFormatter.prototype.format_unchanged = function(context, delta, left) {
    if (typeof left === 'undefined') { return; }
    context.out('<div class="jsondiffpatch-value">');
    this.formatValue(context, left);
    context.out('</div>');
};

HtmlFormatter.prototype.format_movedestination = function(context, delta, left) {
    if (typeof left === 'undefined') { return; }
    context.out('<div class="jsondiffpatch-value">');
    this.formatValue(context, left);
    context.out('</div>');
};

HtmlFormatter.prototype.format_node = function(context, delta, left) {
    // recurse
    var nodeType = (delta._t === 'a') ? 'array': 'object';
    context.out('<ul class="jsondiffpatch-node jsondiffpatch-node-type-' + nodeType + '">');
    this.formatDeltaChildren(context, delta, left);
    context.out('</ul>');
};

HtmlFormatter.prototype.format_added = function(context, delta) {
    context.out('<div class="jsondiffpatch-value">');
    this.formatValue(context, delta[0]);
    context.out('</div>');
};

HtmlFormatter.prototype.format_modified = function(context, delta) {
    context.out('<div class="jsondiffpatch-value jsondiffpatch-left-value">');
    this.formatValue(context, delta[0]);
    context.out('</div>' +
        '<div class="jsondiffpatch-value jsondiffpatch-right-value">');
    this.formatValue(context, delta[1]);
    context.out('</div>');
};

HtmlFormatter.prototype.format_deleted = function(context, delta) {
    context.out('<div class="jsondiffpatch-value">');
    this.formatValue(context, delta[0]);
    context.out('</div>');
};

HtmlFormatter.prototype.format_moved = function(context, delta) {
    context.out('<div class="jsondiffpatch-value">');
    this.formatValue(context, delta[0]);
    context.out('</div><div class="jsondiffpatch-moved-destination">' + delta[1] + '</div>');

    // draw an SVG arrow from here to move destination
    context.out(
        /*jshint multistr: true */
        '<div class="jsondiffpatch-arrow" style="position: relative; left: -34px;">\
        <svg width="30" height="60" style="position: absolute; display: none;">\
        <defs>\
            <marker id="markerArrow" markerWidth="8" markerHeight="8" refx="2" refy="4"\
                   orient="auto" markerUnits="userSpaceOnUse">\
                <path d="M1,1 L1,7 L7,4 L1,1" style="fill: #339;" />\
            </marker>\
        </defs>\
        <path d="M30,0 Q-10,25 26,50" style="stroke: #88f; stroke-width: 2px; fill: none;\
        stroke-opacity: 0.5; marker-end: url(#markerArrow);"></path>\
        </svg>\
        </div>');
    context.hasArrows = true;
};

HtmlFormatter.prototype.format_textdiff = function(context, delta) {
    context.out('<div class="jsondiffpatch-value">');
    this.formatTextDiffString(context, delta[0]);
    context.out('</div>');
};

/* jshint camelcase: true */

var showUnchanged = function(show, node, delay) {
    var el = node || document.body;
    var prefix = 'jsondiffpatch-unchanged-';
    var classes = {
        showing: prefix + 'showing',
        hiding: prefix + 'hiding',
        visible: prefix + 'visible',
        hidden: prefix + 'hidden',
    };
    var list = el.classList;
    if (!list) { return; }
    if (!delay) {
        list.remove(classes.showing);
        list.remove(classes.hiding);
        list.remove(classes.visible);
        list.remove(classes.hidden);
        if (show === false) {
            list.add(classes.hidden);
        }
        return;
    }
    if (show === false) {
        list.remove(classes.showing);
        list.add(classes.visible);
        setTimeout(function(){
            list.add(classes.hiding);
        }, 10);
    } else {
        list.remove(classes.hiding);
        list.add(classes.showing);
        list.remove(classes.hidden);
    }
    var intervalId = setInterval(function(){
        adjustArrows(el);
    }, 100);
    setTimeout(function(){
        list.remove(classes.showing);
        list.remove(classes.hiding);
        if (show === false) {
            list.add(classes.hidden);
            list.remove(classes.visible);
        } else {
            list.add(classes.visible);
            list.remove(classes.hidden);
        }
        setTimeout(function(){
            list.remove(classes.visible);
            clearInterval(intervalId);
        }, delay + 400);
    }, delay);
};

var hideUnchanged = function(node, delay) {
    return showUnchanged(false, node, delay);
};

exports.HtmlFormatter = HtmlFormatter;

exports.showUnchanged = showUnchanged;

exports.hideUnchanged = hideUnchanged;

var defaultInstance;

exports.format = function(delta, left) {
    if (!defaultInstance) {
        defaultInstance = new HtmlFormatter();
    }
    return defaultInstance.format(delta, left);
};